package com.blackto.lib.eventbus;

import com.blackto.lib.eventbus.abstraction.IIntegrationEventHandler;
import com.blackto.lib.eventbus.event.IntegrationEvent;
import lombok.Data;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Component
@Data
public class InMemoryEventBusSubscriptionsManager implements IEventBusSubscriptionsManager {
    private final Map<String, List<SubscriptionInfo>> handlers;

    private final List<Class<? extends IntegrationEvent>> eventTypes;

    public InMemoryEventBusSubscriptionsManager() {
        handlers = new ConcurrentHashMap<>();
        eventTypes = new ArrayList<>();
    }

    @Override
    public boolean isEmpty() {
        return handlers.keySet().isEmpty();
    }

    @Override
    public <TEvent extends IntegrationEvent, THandler extends IIntegrationEventHandler<TEvent>>
    void addSubscription(Class<TEvent> TEventType, Class<THandler> THandlerType) {
        String eventName = getEventName(TEventType);

        if (!hasSubscriptionsForEvent(eventName)) {
            handlers.put(eventName, new ArrayList<>());
        }

        if (handlers.get(eventName).stream().anyMatch(p -> p.getHandlerType().equals(THandlerType))) {
            throw new IllegalArgumentException(
                    "Handler type " + THandlerType.getSimpleName()
                            + " already registered for '" + eventName + "'"
            );
        }

        handlers.get(eventName).add(SubscriptionInfo.typed(THandlerType));

        if (!eventTypes.contains(TEventType)) {
            eventTypes.add(TEventType);
        }
    }

    @Override
    public <TEvent extends IntegrationEvent, THandler extends IIntegrationEventHandler<TEvent>>
    void removeSubscription(Class<TEvent> TEventType, Class<THandler> THandlerType) {
        String eventName = getEventName(TEventType);
        if (!hasSubscriptionsForEvent(eventName)) {
            return;
        }

        Optional<SubscriptionInfo> handlerToRemove = handlers.get(eventName).stream()
                .filter(p -> p.getHandlerType().equals(THandlerType))
                .findFirst();
        if (!handlerToRemove.isPresent()) {
            return;
        }

        handlers.get(eventName).remove(handlerToRemove.get());
        if (!handlers.get(eventName).isEmpty()) {
            return;
        }

        handlers.remove(eventName);

        Optional<Class<? extends IntegrationEvent>> eventType = eventTypes.stream()
                .filter(p -> p.getSimpleName().equals(eventName))
                .findFirst();

        eventType.ifPresent(eventTypes::remove);
    }


    @Override
    public boolean hasSubscriptionsForEvent(String eventName) {
        return handlers.containsKey(eventName);
    }

    @Override
    public Class<? extends IntegrationEvent> getEventTypeByName(String eventName) {
        Optional<Class<? extends IntegrationEvent>> eventType = eventTypes.stream()
                .filter(p -> p.getSimpleName().equals(eventName))
                .findFirst();
        return eventType.orElse(null);
    }

    @Override
    public void clear() {
        handlers.clear();
    }

    @Override
    public List<SubscriptionInfo> getHandlersForEvent(String eventName) {
        return handlers.get(eventName);
    }

    @Override
    public String getEventName(Class<? extends IntegrationEvent> eventClass) {
        return eventClass.getSimpleName();
    }
}